#include "z_http.h"
#include "z_string.h"
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netdb.h>
#include <sys/types.h>


/*Function definition*/
void Die(char *mess){
	perror(mess);
	fprintf(stderr,"In %s:%s:%d",__FILE__,__func__,__LINE__);
	exit(1);
}

static const char * get_protocol(const char * url){
	const char * first_colon;
	if ((first_colon = strstr(url,"://"))){
		char * protocol = (char *)malloc(MAX_PROTOCOL_SIZE*sizeof(char));
		bzero(protocol,MAX_PROTOCOL_SIZE);
		strncpy(protocol,url,first_colon - url + 3);
		return protocol;
	}
	else{
		return NULL;
	}
}

static const char * get_host(const char * url){
	size_t proto_len = 0;
	if (get_protocol(url))
		proto_len = strlen(get_protocol(url));
	url = url + proto_len;
	const char * first_slash = index(url,'/');
	if (!first_slash){
		return url;
	}else{
		char * host = (char *)calloc(MAX_HOST_SIZE,sizeof(char));
		strncpy(host,url,first_slash - url);
		return host;
	}
}
static const char * get_uri(const char * url){
	size_t proto_len = 0;
	if (get_protocol(url))
		proto_len = strlen(get_protocol(url));
	size_t host_len = strlen(get_host(url));
	url = url + proto_len + host_len;
	if (*url != '\0')
		return url;
	else{
		return "/";
	}
}

static struct field * get_data(const char * url, unsigned int * count_kv){
	*count_kv = 0;
	const char * question = index(url,'?');
	if (!question){
		return NULL;
	}
	const char * equ = index(url,'=');
	const char * p = url;
	while (*p != '\0'){
		if (*p == '=')
			++(*count_kv);
		++p;
	}
	unsigned int i = 0; 
	unsigned int tmp_len = 0;
	struct field * data = (struct field * )malloc(*count_kv * sizeof(struct field));
	do{
		tmp_len = equ - question;
		data[i].key = (char *)calloc(tmp_len,sizeof(char));
		strncpy(data[i].key,question+1,tmp_len-1);
		question = index(equ,'&');
		tmp_len = (question)?(question-equ):(strlen(equ));
		data[i].value = (char *)calloc(tmp_len,sizeof(char));
		strncpy(data[i].value,equ+1,tmp_len-1);
		equ = index(equ+1,'=');
		++i;
	}while(i < *count_kv);
	return data;
}
static struct field * get_params(const char * uri, unsigned int * n){
	return NULL;
}
static void free_kv(struct field * data, unsigned int n){
	if (!data)
		return;
	for (unsigned int i = 0; i < n; ++i){
		free(data[i].key);
		free(data[i].value);
	}
	free(data);
	data = NULL;
}

char * data2url(struct field * data, int d,int size){
	char * cmpt_uri = (char *)malloc(size * sizeof(char));
	if (!cmpt_uri)	Die("malloc failed!");
	bzero(cmpt_uri, MAX_URI_SIZE);
	for (int i = 0; i < d; ++i){
		char * temp_key;
		char * temp_value;
		multicat(MULTICAT_EOF,
				cmpt_uri,
				temp_key = url_encode(data[i].key,strlen(data[i].key),NULL),
				"=",
				temp_value = url_encode(data[i].value,strlen(data[i].value),NULL),//URL_ENCODE for every value and key
				MULTICAT_EOF);
		free(temp_value);
		free(temp_key);
		if (i != d-1) strcat(cmpt_uri,"&");
	}//for
	return cmpt_uri;
}//data2url()


/*Create a new http request header structure*/
struct http_header * new_http_header(
		char * method,
		char * uri,
		char * protocol,
		char * host,
		struct field params[], int c,
		struct field data[], int d)
{
	struct http_header * header = (struct http_header *) malloc(sizeof(struct http_header));
	header->method 		= method;
	/*Construct the GET request url*/
	if (strcmp(method,"GET") == 0 && d > 0){
		char * cmpt_uri = (char *)malloc(MAX_URI_SIZE * sizeof(char));
		if (!cmpt_uri)	Die("malloc failed!");
		bzero(cmpt_uri, MAX_URI_SIZE);
		char * tmp_data_str = data2url(data,d,MAX_URI_SIZE);
		multicat(MULTICAT_EOF,cmpt_uri,uri,"?",tmp_data_str,MULTICAT_EOF);
		free(tmp_data_str);
		header->uri	= cmpt_uri;
	}else{
		header->uri	= uri;
	}//if else

	header->host		= host;
	header->protocol	= protocol;
	header->params 		= params;
	header->c			= c;
	header->data		= data;
	header->d			= d;
	return header;
}//new_http_header()

/*Create a HTTP request header string*/
char * new_http_header_str(struct http_header * header){
	char * str = (char *)malloc(BUFFSIZE * sizeof(char));
	if (!str)	Die("malloc failed!");
	bzero(str, BUFFSIZE);
	multicat(MULTICAT_EOF,
			str,
			header->method , " " , header->uri , " " , header->protocol , "\r\n",
			"HOST: ", header->host , "\r\n",
			MULTICAT_EOF);
	/*Construct HTTP request header parameters*/
	for (int i = 0; i < header->c; ++i)
		multicat(MULTICAT_EOF,
				str,
				header->params[i].key ,": " ,header->params[i].value ,"\r\n",
				MULTICAT_EOF);
	strcat(str,"\r\n");
	/*Construct the POST request data*/
	char * post_data = data2url(header->data,header->d,MAX_POST_SIZE);
	multicat(MULTICAT_EOF,
			str,
			DEFAULT_HTTP_HEADERS,
			"Content-Length: ",
			int2str(strlen(post_data)), "\r\n",
			post_data, "\r\n",
			MULTICAT_EOF);
	free(header);
	header = NULL;
	free(post_data);
	return str;
}//new_http_header_str()

/*lookup the host IP*/
char * nslkup(const char * host_name){
	struct hostent *host;
	struct in_addr h_addr;
	if ((host = gethostbyname(host_name)) == NULL){
		fprintf(stderr,"nslookup failed on '%s' \n",host_name);
		exit(-1);
	}//fi
	h_addr.s_addr = *((unsigned long *) host->h_addr_list[0]);
	return inet_ntoa(h_addr);
}//nslookup()

#ifdef GLOBAL_RECV_BUF
char global_recv_buf[RECEIVE_BUFFSIZE];
#endif
/*Send data to any port on a host*/
char * send_to(const char * host, int port, const char * data){
	int sock;
	struct sockaddr_in server;
	/*Create the TCP socket */
	if ((sock = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP)) < 0){
		Die("Failed to create socket");
	}

	/* Construct the server sockaddr_in structure */
	memset(&server, 0, sizeof(server));					/*Clear struct*/
	server.sin_family 		= AF_INET;					/*Internet/IP*/
	server.sin_addr.s_addr 	= inet_addr(nslkup(host));	/*IP address*/
	server.sin_port			= htons(port);				/*server port*/

	/*Establesh connection*/
	if (connect(sock, (struct sockaddr *) &server, sizeof(server)) < 0){
		Die("Failed to connect with server");
	}

	/* Send data to server */
	size_t datalen = strlen(data);
	if (send(sock, data, datalen, 0) != datalen){
		Die("Mismatch in number of sent bytes");
	}
	/*Receive data back from the server */
	char * buffer = (char *)malloc(RECEIVE_BUFFSIZE*sizeof(char));
	if (!buffer)
		Die("malloc failed!");
	char *ptr = buffer;
	int received = 0,bytes = -1, current_size = RECEIVE_BUFFSIZE;
	while ((bytes = recv(sock, ptr, current_size-received, 0)) > 0){ //Here you shold use pthread lib to implement sync between write and read.
		received += bytes;
		ptr 	 += bytes;
		if (received >= current_size){
			buffer = (char*)realloc(buffer,current_size*2);
		}
	}
	buffer[received] = '\0';

	close(sock);
	return buffer;
}//send_to()

/*Send a GET or POST http request to server with port 80*/
char * http_request(
		const char * method,
		const char * host,
		const char * uri,
		const char * http_edition,
		struct field * params, int n,
		struct field * data, int m,
		int echo)
{
	/*Construct the GET request url*/
	char * post_data = data2url(data,m,MAX_URI_SIZE);
	if (strcmp(method,"GET") == 0 && m > 0){
		char * cmpt_uri = (char *)malloc(MAX_URI_SIZE * sizeof(char));
		if (!cmpt_uri)	Die("malloc failed!");
		bzero(cmpt_uri, MAX_URI_SIZE);
		multicat(MULTICAT_EOF,cmpt_uri,uri,"?",post_data,MULTICAT_EOF);
		uri	= cmpt_uri;
	}
	/*Create string for send*/
	char * str = (char *)malloc(BUFFSIZE * sizeof(char));
	if (!str)	Die("malloc failed!");
	bzero(str, BUFFSIZE);
	multicat(MULTICAT_EOF,
			str,
			method , " " , uri , " " , http_edition , "\r\n",
			"HOST: ", host , "\r\n",
			MULTICAT_EOF);
	/*Construct HTTP request header parameters*/
	for (int i = 0; i < n; ++i)
		multicat(MULTICAT_EOF,
				str,
				params[i].key ,": " ,params[i].value ,"\r\n",
				MULTICAT_EOF);
	strcat(str,"\r\n");
	/*Construct the POST request data*/
	multicat(MULTICAT_EOF,
			str,
			"Content-Type: application/x-www-form-urlencoded\r\nContent-Length: ",
			int2str(strlen(post_data)), "\r\n",
			post_data, "\r\n",
			MULTICAT_EOF);
	free(post_data);

	if (echo)
		fprintf(stdout,"Sending:\n%s\n", str);
	char * response = send_to(host, 80, str);
	free(str);
	return response;
}//http_request()

/*Delete http header from received data*/
char * filte_http_response_header(char * rps){
	if (rps == NULL) return NULL;
	char * start = strstr(rps,"\r\n\r\n") + 4;
	return start;
}//filte_http_response_header()

/*Delete html content ,just save the other content like json or xml*/
char * filte_html_from(char * receive){
	if (receive == NULL) return NULL;
	char * end = strstr(receive,"<html>");
	if (end){
		char * p = receive;
		unsigned int len = 0;
		while(p != end){
			p++;
			len++;
		}
		char * json = (char *)malloc(len*sizeof(char));
		if (!json)	Die("malloc failed!");
		strncpy(json,receive,len);
		json[len] = '\0';
		return json;
	}else{
		return receive;
	}
}//filte_html_from()


char * zurl(const char * url, char * opt){
		const char * host = get_host(url);
		const char * uri = get_uri(url);
		unsigned int m,n;
		m = n = 0;
		struct field * data = get_data(uri,&m);
		struct field * params = get_params(uri,&n);
		char * buffer = http_request("GET",
				host,
				uri,
				HTTP_EDITION,
				params,
				m,
				data,
				n,1);
		free_kv(data,m);
		free_kv(params,n);
		return buffer;
}

